#include "RemoteContextMenuHandler.h"

#include "../browser/RemoteFrame.h"
#include "../callback/RemoteCefRunContextMenuCallback.h"

#include <boost/container/container_fwd.hpp>
#include <vector>

namespace {

thrift_codegen::ContextMenuParams convertParams(
    const CefRefPtr<CefContextMenuParams>& params) {
  thrift_codegen::ContextMenuParams thriftParams;

  if (!params) {
    return thriftParams;
  }

  thriftParams.x = params->GetXCoord();
  thriftParams.y = params->GetYCoord();
  thriftParams.link_url = params->GetLinkUrl().ToString();
  thriftParams.unfiltered_link_url = params->GetUnfilteredLinkUrl().ToString();
  thriftParams.source_url = params->GetSourceUrl().ToString();
  thriftParams.page_url = params->GetPageUrl().ToString();
  thriftParams.frame_url = params->GetFrameUrl().ToString();
  thriftParams.frame_charset = params->GetFrameCharset();
  thriftParams.media_type = static_cast<int>(params->GetMediaType());
  thriftParams.media_state_flags = params->GetMediaStateFlags();
  thriftParams.selected_text = params->GetSelectionText().ToString();
  thriftParams.misspelled_word = params->GetMisspelledWord().ToString();
  // not supported: params->GetDictionarySuggestions()
  thriftParams.is_editable = params->IsEditable();
  thriftParams.edit_state_flags = params->GetEditStateFlags();
  thriftParams.is_custom_menu = params->IsCustomMenu();
  thriftParams.type_flags = params->GetTypeFlags();
  thriftParams.has_image_contents = params->HasImageContents();

  return thriftParams;
}

thrift_codegen::MenuItemType::type convert_menu_type(
    CefMenuModel::MenuItemType type) {
  switch (type) {
    case CefMenuModel::MenuItemType::MENUITEMTYPE_NONE:
      return thrift_codegen::MenuItemType::MENUITEMTYPE_NONE;

    case CefMenuModel::MenuItemType::MENUITEMTYPE_COMMAND:
      return thrift_codegen::MenuItemType::MENUITEMTYPE_COMMAND;

    case CefMenuModel::MenuItemType::MENUITEMTYPE_CHECK:
      return thrift_codegen::MenuItemType::MENUITEMTYPE_CHECK;

    case CefMenuModel::MenuItemType::MENUITEMTYPE_RADIO:
      return thrift_codegen::MenuItemType::MENUITEMTYPE_RADIO;

    case CefMenuModel::MenuItemType::MENUITEMTYPE_SEPARATOR:
      return thrift_codegen::MenuItemType::MENUITEMTYPE_SEPARATOR;

    case CefMenuModel::MenuItemType::MENUITEMTYPE_SUBMENU:
      return thrift_codegen::MenuItemType::MENUITEMTYPE_SUBMENU;

    default:
      LOG(ERROR) << "RemoteContextMenuHandler: Unknown cef menu item type: "
                 << type;
      return thrift_codegen::MenuItemType::MENUITEMTYPE_NONE;
  }
}

/* TODO: Non-recursive DFS
std::vector<thrift_codegen::MenuItem> to_thrift(CefRefPtr<CefMenuModel> model) {
  std::vector<thrift_codegen::MenuItem> result;
  std::stack<std::pair<CefRefPtr<CefMenuModel>,
std::vector<thrift_codegen::MenuItem>*>> stack;

  // Initialize stack with the root menu model and output list
  stack.push({model, &result});

  while (!stack.empty()) {
    auto [current_model, current_result] = stack.top();
    stack.pop();

    for (int i = 0; i < current_model->GetCount(); ++i) {
      thrift_codegen::MenuItem item;
      item.label = current_model->GetLabelAt(i).ToString();
      item.type = convert_menu_type(current_model->GetTypeAt(i));
      item.command_id = current_model->GetCommandIdAt(i);
      item.group_id = current_model->GetGroupIdAt(i);
      item.visible = current_model->IsVisibleAt(i);
      item.enabled = current_model->IsEnabledAt(i);
      item.checked = current_model->IsCheckedAt(i);

      // If the current item is a submenu, prepare to process it later
      if (item.type == thrift_codegen::MenuItemType::MENUITEMTYPE_SUBMENU) {
        // Submenu items are placed in `item.sub_menu`, which is processed
separately item.sub_menu.emplace(); // Initialize sub_menu
        stack.push({current_model->GetSubMenuAt(i), &item.sub_menu.value()});
      }

      current_result->push_back(std::move(item));
    }
  }

  return result;
}
 */

std::vector<thrift_codegen::MenuItem> to_thrift(CefRefPtr<CefMenuModel> model) {
  std::vector<thrift_codegen::MenuItem> result;
  for (int i = 0; i < model->GetCount(); ++i) {
    result.emplace_back();
    result.back().label = model->GetLabelAt(i).ToString();
    result.back().type = convert_menu_type(model->GetTypeAt(i));
    result.back().command_id = model->GetCommandIdAt(i);
    result.back().group_id = model->GetGroupIdAt(i);
    result.back().visible = model->IsVisibleAt(i);
    result.back().enabled = model->IsEnabledAt(i);
    result.back().checked = model->IsCheckedAt(i);
    result.back().command_id = model->GetCommandIdAt(i);
    if (result.back().type ==
        thrift_codegen::MenuItemType::MENUITEMTYPE_SUBMENU) {
      result.back().sub_menu = to_thrift(model->GetSubMenuAt(i));
    }
  }

  return result;
}

void to_cef(CefRefPtr<CefMenuModel> out,
            const std::vector<thrift_codegen::MenuItem>& in) {
  out->Clear();
  for (size_t i = 0; i < in.size(); ++i) {
    switch (in[i].type) {
      case thrift_codegen::MenuItemType::MENUITEMTYPE_COMMAND:
        out->AddItem(in[i].command_id, in[i].label);
        break;
      case thrift_codegen::MenuItemType::MENUITEMTYPE_CHECK:
        out->AddCheckItem(in[i].command_id, in[i].label);
        out->SetCheckedAt(i, in[i].checked);
        break;
      case thrift_codegen::MenuItemType::MENUITEMTYPE_RADIO:
        out->AddRadioItem(in[i].command_id, in[i].label, in[i].group_id);
        out->SetCheckedAt(i, in[i].checked);
        break;
      case thrift_codegen::MenuItemType::MENUITEMTYPE_SEPARATOR:
        out->AddSeparator();
        break;
      case thrift_codegen::MenuItemType::MENUITEMTYPE_SUBMENU: {
        auto sub_menu = out->AddSubMenu(in[i].command_id, in[i].label);
        to_cef(sub_menu, in[i].sub_menu);
        break;
      }
      case thrift_codegen::MenuItemType::MENUITEMTYPE_NONE:
        continue;
    }

    out->SetEnabledAt(i, in[i].enabled);
    out->SetVisibleAt(i, in[i].visible);
  }
}

}  // namespace

RemoteContextMenuHandler::RemoteContextMenuHandler(
    const int my_bid,
    const std::shared_ptr<RpcExecutor>& my_service)
    : myBid(my_bid), myService(my_service) {}

RemoteContextMenuHandler::~RemoteContextMenuHandler() = default;

void RemoteContextMenuHandler::OnBeforeContextMenu(
    CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame,
    CefRefPtr<CefContextMenuParams> params,
    CefRefPtr<CefMenuModel> model) {
  LNDC();

  RemoteFrame::Holder frm(frame);
  const auto thriftParams = convertParams(params);
  const std::vector<thrift_codegen::MenuItem> menu_model = to_thrift(model);

  std::vector<thrift_codegen::MenuItem> result;
  myService->exec([&](const JavaService& s) {
    s->ContextMenuHandler_OnBeforeContextMenu(
        result, myBid, frm.serverId(), thriftParams, menu_model);
  });

  to_cef(model, result);
}

bool RemoteContextMenuHandler::RunContextMenu(
    CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame,
    CefRefPtr<CefContextMenuParams> params,
    CefRefPtr<CefMenuModel> model,
    CefRefPtr<CefRunContextMenuCallback> callback) {
  LNDC();
  RemoteFrame::Holder frm(frame);
  const auto thriftParams = convertParams(params);
  const std::vector<thrift_codegen::MenuItem> menu_model = to_thrift(model);
  RemoteCefRunContextMenuCallback* callback_wrapper =
      RemoteCefRunContextMenuCallback::wrapDelegate(callback);
  bool result = myService->exec<bool>(
      [&](const JavaService& s) -> bool {
        return s->ContextMenuHandler_RunContextMenu(
            myBid, frm.serverId(), thriftParams, menu_model,
            callback_wrapper->serverId());
      },
      false);

  if (!result) {
    RemoteCefRunContextMenuCallback::dispose(callback_wrapper->getId());
  }
  return result;
}

bool RemoteContextMenuHandler::OnContextMenuCommand(
    CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame,
    CefRefPtr<CefContextMenuParams> params,
    int command_id,
    EventFlags event_flags) {
  LNDC();
  RemoteFrame::Holder frm(frame);
  const auto thriftParams = convertParams(params);
  return myService->exec<bool>([&](const JavaService& s) {
    return s->ContextMenuHandler_OnContextMenuCommand(
        myBid, frm.serverId(), thriftParams, command_id,
        event_flags);
  }, false);
}

void RemoteContextMenuHandler::OnContextMenuDismissed(
    CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame) {
  LNDC();
  RemoteFrame::Holder frm(frame);
  myService->exec([&](const JavaService& s) {
    s->ContextMenuHandler_OnContextMenuDismissed(myBid, frm.serverId());
  });
}